package com.chen.webservice.service;

import cn.hutool.core.collection.CollectionUtil;
import com.chen.base.http.InvokeResponseEntity;
import com.chen.ga.Fitness;
import com.chen.ga.GeneticAlgorithm;
import com.chen.ga.IterartionListener;
import com.chen.ga.Population;
import com.chen.ga.fitness.MyVectorFitness;
import com.chen.webservice.Utils.RuleChain;
import com.chen.webservice.Utils.RuleConstants;
import com.chen.webservice.model.*;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import javafx.fxml.LoadException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;

import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
public class ArrangeService {


//    private static final int OPEN_TIME = 64800;   // 18:00
//    private static final int CLOSE_TIME = 82800;  // 23:00
    private int openTime; // 开屏时间
    private int closeTime;    // 关屏时间

    @Autowired
    private RestTemplate restTemplate;

    @Async
    public void arrangeProgram(ConfigureParameterDTO dto) {
        this.openTime = dto.getFixProgram().getIsArrage() ? dto.getOpenTime() + dto.getFixProgram().getPlayTime() : dto.getOpenTime();
        this.closeTime = dto.getFixProgram().getIsArrage() ? dto.getCloseTime() - dto.getFixProgram().getPlayTime() : dto.getCloseTime();
        Population<MyOrder> population = createInitialPopulation(10, openTime, closeTime, dto.getOrders(),
                dto.getOnlyArrangeFirst30Minute(), dto.getBusinessRate(), dto.getFixOrders());

        Fitness<MyOrder, Double> fitness = new MyVectorFitness();

        List<Integer> rules = initRule(dto.getNotAdjacentPrograme());

        GeneticAlgorithm<MyOrder, Double> ga = new GeneticAlgorithm<MyOrder, Double>(population, fitness, rules);

        addListener(ga);

        ga.evolve(10000);

        log.info("finished!");

        List<ProgramPlayOrderDTO> arrangeResult = sout(ga.getPopulation().getChromosomeByIndex(0).getOrders());
        if (dto.getOnlyArrangeFirst30Minute()) {
            arrangeResult = fillRestTime(arrangeResult);
        }
        // 添加开屏/关屏广告
        if (dto.getFixProgram().getIsArrage()) {
            long nowDay = getTodayZeroPointTimestamps();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            ProgramPlayOrderDTO startMaterial = new ProgramPlayOrderDTO();
            ProgramPlayOrderDTO endMaterial = new ProgramPlayOrderDTO();

            if (Objects.nonNull(dto.getStartMaterialId())) {
                startMaterial.setId(dto.getStartMaterialId());
                startMaterial.setName("开屏节目");
                startMaterial.setCategory("自制");
                long startTime = nowDay + 1000L * dto.getOpenTime();
                String startTimeStr = sdf.format(new Date(startTime)); // 时间戳转换日期
                startMaterial.setPlayStartTime(startTimeStr);
                long endTime = nowDay + 1000L * (dto.getOpenTime() + dto.getFixProgram().getPlayTime()) ;
                String endTimeStr = sdf.format(new Date(endTime)); // 时间戳转换日期
                startMaterial.setPlayEndTime(endTimeStr);
                startMaterial.setPlayTime(dto.getFixProgram().getPlayTime());
                arrangeResult.add(0, startMaterial);
            }

            if (Objects.nonNull(dto.getEndMaterialId())) {
                endMaterial.setId(dto.getEndMaterialId());
                endMaterial.setName("关屏节目");
                endMaterial.setCategory("自制");
                long endTime = nowDay + 1000L * dto.getCloseTime();
                String endTimeStr = sdf.format(new Date(endTime)); // 时间戳转换日期
                endMaterial.setPlayEndTime(endTimeStr);
                long startTime = nowDay + 1000L * (dto.getCloseTime() - dto.getFixProgram().getPlayTime());
                String startTimeStr = sdf.format(new Date(startTime)); // 时间戳转换日期
                endMaterial.setPlayStartTime(startTimeStr);
                endMaterial.setPlayTime(dto.getFixProgram().getPlayTime());
                arrangeResult.add(endMaterial);
            }
        }

//        return arrangeResult;

        // 返回体
        ArrangeResultReturnDTO resultReturnDTO = new ArrangeResultReturnDTO();
        resultReturnDTO.setDate(dto.getDate());
        resultReturnDTO.setLandmarkId(dto.getLandmarkId());
        resultReturnDTO.setList(arrangeResult);

        String url = "http://106.15.51.118:8087/api/eam/callback/program";
        ResponseEntity<InvokeResponseEntity> response = restTemplate.postForEntity(url, resultReturnDTO, InvokeResponseEntity.class);
        HttpStatus statusCode = response.getStatusCode();

        if (!statusCode.equals(HttpStatus.OK)) {
            log.error("statusCode： {}，请求出错.", statusCode.value());
        } else {
            InvokeResponseEntity body = response.getBody();
            if ("0000".equals(body.getCode())) {
                log.info("请求成功.");
            } else {
                log.error("调用失败，status: {}", body.getCode());
            }
        }
    }

    private void remoteCall(Object resultReturnDTO, String url) {
        ResponseEntity<InvokeResponseEntity> response = restTemplate.postForEntity(url, resultReturnDTO, InvokeResponseEntity.class);
        HttpStatus statusCode = response.getStatusCode();

        if (!statusCode.equals(HttpStatus.OK)) {
            log.error("statusCode： {}，请求出错.", statusCode.value());
        } else {
            InvokeResponseEntity body = response.getBody();
            if ("0000".equals(body.getCode())) {
                log.info("请求成功.");
            } else {
                log.error("调用失败，status: {}", body.getCode());
            }
        }
    }

    /**
     * 剩余播放时间的广告由前30分钟的广告进行轮询播放
     *
     * @param arrangeResult
     * @return
     */
    private List<ProgramPlayOrderDTO> fillRestTime(List<ProgramPlayOrderDTO> arrangeResult) {
        List<ProgramPlayOrderDTO> finalArrangeResult = new ArrayList<>(arrangeResult);
        // 塞数据
        int count = 2;
        while (this.openTime + 1800 * count <= this.closeTime) {
            int finalCount = count;
            List<ProgramPlayOrderDTO> addArrangeResult = arrangeResult.stream().map(result -> getProgramPlayOrderDTO(finalCount, result)).collect(Collectors.toList());
            finalArrangeResult.addAll(addArrangeResult);
            count++;
        }
        if (this.openTime + 1800 * count > this.closeTime &&
                this.openTime + 1800 * (count - 1) < this.closeTime) {
            // 无法整除30分钟，遍历塞数据
            long sumPlayTime = this.openTime + 1800L * (count - 1);
            for (ProgramPlayOrderDTO playOrderDTO : arrangeResult) {
                long playTime = getProgramPlayTime(playOrderDTO.getPlayEndTime(), playOrderDTO.getPlayStartTime());
                if (sumPlayTime + playTime < this.closeTime) {
                    ProgramPlayOrderDTO programPlayOrderDTO = getProgramPlayOrderDTO(count, playOrderDTO);
                    finalArrangeResult.add(programPlayOrderDTO);
                    sumPlayTime += playTime;
                }
            }
        }
        return finalArrangeResult;
    }

    private ProgramPlayOrderDTO getProgramPlayOrderDTO(int count, ProgramPlayOrderDTO result) {
        ProgramPlayOrderDTO programPlayOrderDTO = new ProgramPlayOrderDTO();
        programPlayOrderDTO.setId(result.getId());
        programPlayOrderDTO.setName(result.getName());
        programPlayOrderDTO.setCategory(result.getCategory());
        programPlayOrderDTO.setPlayStartTime(convertTime(result.getPlayStartTime(), count));
        programPlayOrderDTO.setPlayEndTime(convertTime(result.getPlayEndTime(), count));
        return programPlayOrderDTO;
    }

    /**
     * 计算广告的播放时长
     *
     * @param endTime
     * @param startTime
     * @return
     */
    private long getProgramPlayTime(String endTime, String startTime) {
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return Duration.between(LocalDateTime.parse(startTime, df), LocalDateTime.parse(endTime, df)).toMillis() / 1000;
    }

    /**
     * 计算播放时间
     *
     * @param time
     * @param count
     * @return
     */
    private String convertTime(String time, int count) {
        DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long playTime = LocalDateTime.parse(time, df).toInstant(ZoneOffset.of("+8")).toEpochMilli() + 1800L * (count - 1) * 1000;
        return sdf.format(new Date(playTime));
    }

    /**
     * 规则初始化
     *
     * @return
     */
    private static List<Integer> initRule(ConfigureParameterDTO.NotAdjacentProgram notAdjacentProgram) {
        List<Integer> rules = RuleChain.forceCheckList;
        List<Integer> weakRules = Lists.newArrayList(1, 2, 3);
        if (notAdjacentProgram.getGovernmentAndBusiness()) {
            weakRules.add(101);
        }
        if (notAdjacentProgram.getSameSubType()) {
            weakRules.add(102);
        }
        if (notAdjacentProgram.getSameWordType()) {
            weakRules.add(103);
        }
        if (notAdjacentProgram.getSameColorType()) {
            weakRules.add(104);
        }
        rules.addAll(weakRules);
        return rules;
    }

    private static void testFuc() {
        String c = "";
        List<String> list = new ArrayList<>();
        for(int i=0;i<20;i++){
            list.add("X");
        }
        for(int i=0;i<10;i++){
            list.add("O");
        }
        String before = Joiner.on(" ").join(list);
        for (int i=0;i<10;i++){
        Collections.shuffle(list);}
        String after = Joiner.on(" ").join(list);

        System.out.println(before);
        System.out.println(c);
        System.out.println(after);
    }


    private static String generateMsg(Map<String, List<String>> appMap) {
        String content = "问题类型:%s, 涉及包名:%s\n";
        StringBuilder sb = new StringBuilder().append("\"");
        for (Map.Entry<String, List<String>> entry : appMap.entrySet()) {
            String key = entry.getKey();
            List<String> values = entry.getValue();
            String msg = String.format(content, key, Joiner.on(",").join(values));
            sb.append(msg);
        }
        sb.append("\"");
        return sb.toString();
    }

    /**
     * @description: 打印最终排片
     * @params: [orders]
     * @return: void
     * @author: chenzhiwen
     * @dateTime: 2021/10/21 下午7:13
     */
    private List<ProgramPlayOrderDTO> sout(List<Order> orders) {
        long nowDay = getTodayZeroPointTimestamps();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        long startTime = nowDay + 1000L * this.openTime;
//        log.debug("节目单：");
        boolean notFilter = true;
//        notFilter = false;

        List<ProgramPlayOrderDTO> programPlayOrderDTOList = new ArrayList<>();
        for (Order order : orders) {

            Long orderId = order.getId();
            String sd = sdf.format(new Date(startTime)); // 时间戳转换日期

            if (order.getPlayType().equals(RuleConstants.PlayType.COVER.getValue())) {
                // 包屏广告根据播放时长进行分割
                int totalTime = order.getEndTime() - order.getStartTime();
                int coverPlayTimes = (int) Math.ceil((double) totalTime / order.getPlayTime());
                for (int i = 0; i < coverPlayTimes; i++) {
                    sd = sdf.format(new Date(startTime)); // 时间戳转换日期
                    ProgramPlayOrderDTO programPlayOrderDTO = getProgramPlayOrderDTO(order, orderId, sd);

                    int realPlayTime;
                    if (i == coverPlayTimes - 1) {
                        int resPlayTime = totalTime - (i) * order.getPlayTime();    // 剩余允许播放时长
                        realPlayTime = resPlayTime <= order.getPlayTime() ? resPlayTime : order.getPlayTime();
                    } else {
                        realPlayTime = order.getPlayTime();
                    }
                    programPlayOrderDTO.setPlayTime(realPlayTime);

                    startTime += realPlayTime * 1000;
                    programPlayOrderDTO.setPlayEndTime(sdf.format(new Date(startTime)));
                    programPlayOrderDTOList.add(programPlayOrderDTO);
                }
            } else {
                sd = sdf.format(new Date(startTime)); // 时间戳转换日期
                // 非包屏广告，还是按照原来逻辑
                ProgramPlayOrderDTO programPlayOrderDTO = getProgramPlayOrderDTO(order, orderId, sd);
                programPlayOrderDTO.setPlayTime(order.getPlayTime());

                startTime += order.getPlayTime() * 1000;
                programPlayOrderDTO.setPlayEndTime(sdf.format(new Date(startTime)));
                programPlayOrderDTOList.add(programPlayOrderDTO);
            }
        }
        log.debug("总节目数量：" + orders.size());

//String sd = sdf.format(new Date(Long.parseLong(beginDate))); // 时间戳转换日期

        return programPlayOrderDTOList;
    }

    private ProgramPlayOrderDTO getProgramPlayOrderDTO(Order order, Long orderId, String sd) {
        ProgramPlayOrderDTO programPlayOrderDTO = new ProgramPlayOrderDTO();
        programPlayOrderDTO.setId(orderId.toString());
        programPlayOrderDTO.setName(order.getName());
        programPlayOrderDTO.setCategory(RuleConstants.OrderType.findByType(order.getCategory()).getKey());
        programPlayOrderDTO.setPlayStartTime(sd);


        log.debug("时间：" + sd);
        log.debug(", 节目名称：" + order.getName());
        log.debug(", 节目分类：" + RuleConstants.OrderType.findByType(order.getCategory()).getKey());
        log.debug(", 播出类型：" + RuleConstants.PlayType.findByType(order.getPlayType()).getKey());
        if (Objects.nonNull(order.getColor())) {
            log.debug(", 色系：" + RuleConstants.ColorType.findByType(order.getColor()).getKey());
        }
        if (Objects.nonNull(order.getStyle())) {
            log.debug(", 风格：" + RuleConstants.StyleType.findByType(order.getStyle()).getKey());
        }

        return programPlayOrderDTO;
    }

    public static Long getTodayZeroPointTimestamps() {
        Long currentTimestamps = System.currentTimeMillis();
        Long oneDayTimestamps = Long.valueOf(60 * 60 * 24 * 1000);
        return currentTimestamps - (currentTimestamps + 60 * 60 * 8 * 1000) % oneDayTimestamps;
    }


    /**
     * @description: 初始化初代个体
     *
     * @params: [populationSize]
     * @return: com.chen.ga.Population<com.chen.webservice.Demo2.MyOrder>
     * @author: chenzhiwen
     * @dateTime: 2021/10/21 下午7:13
     */
    private static Population<MyOrder> createInitialPopulation(int populationSize, Integer openTime, Integer closeTime,
                                                               List<Order> orders, Boolean onlyArrangeFirst30Minute,
                                                               Double businessRate, List<ConfigureParameterDTO.FixOrder> fixOrders) {

        Population<MyOrder> population = new Population<MyOrder>();
        if (onlyArrangeFirst30Minute) {
            closeTime = openTime + 1800;
        }
        MyOrder base = new MyOrder(openTime, closeTime, orders, fixOrders);

        processFreq(base);
        processTime(base, onlyArrangeFirst30Minute, businessRate);

        boolean coverFirst = true;
        for (int i = 0; i < populationSize; i++) {
            // each member of initial population
            // is mutated clone of base chromosome
            MyOrder chr = base.mutate();
            if (coverFirst) {
                try {
//                  checkCover(chr.orders, chr.covOrders);
                    coverFirst = false;
                    population.addChromosome(chr);
                    continue;
                } catch (Exception e) {
                    log.error("check cover order error");
                }
            }
//            Collections.shuffle(chr.orders);
            population.addChromosome(chr);
        }
        return population;
    }

    /**
     * 根据总播放时间，添加满足播放时长的定时广告和原创广告
     *
     * @param base
     */
    private static void processTime(MyOrder base, Boolean onlyArrangeFirst30Minute, Double businessRate) {
        List<Order> orders = base.getOrders();   // 定时广告
        Iterator<Order> iterator = orders.iterator();

        List<Order> originOrders = base.getOriOrders();
        List<Order> coverOrders = base.getCovOrders();

        long fixTime = base.getEndTime() - base.getStartTime();
        // 根据总播放时间，添加满足播放时长的定时广告和原创广告
        fillTime(fixTime, orders, coverOrders, originOrders, onlyArrangeFirst30Minute, base.getStartTime(), businessRate);

    }

    /**
     * 填充播放时间
     *
     * @param fixTime
     * @param orders
     * @param coverOrders
     * @param originOrders
     */
    private static void fillTime(long fixTime, List<Order> orders, List<Order> coverOrders, List<Order> originOrders,
                                 Boolean onlyArrangeFirst30Minute, Integer startTime, Double businessRate) {
        if (fixTime <= 0) {
            return; // 节目时间不正确
        }
        List<Order> resOrders = new ArrayList<>();
        Long fixTime1;
        if (onlyArrangeFirst30Minute) {
            // 只排前30分钟广告
            Integer endTime = startTime + 30 * 60;
            coverOrders.forEach(coverOrder -> {
                if (coverOrder.getStartTime() >= startTime && coverOrder.getEndTime() <= endTime) {
                    resOrders.add(coverOrder);
                }
            });
            int orderTime = getPlayedTime(resOrders);
            fixTime1 = fixTime - orderTime;    // 总播放时间 - 包屏时间
        } else {
            // 设定的播放时间段内都需要排单
            fixTime1 = arrangeAllTime(fixTime, coverOrders, resOrders);
        }

        if (fixTime1 > 0) { // 包屏时间未用完
            fixTime1 = shuffleOrder(resOrders, orders, fixTime1, true, fixTime,  businessRate); // 包屏广告中添加定时节目
            shuffleOrder(resOrders, originOrders, fixTime1, false, fixTime,  businessRate);
        }

        orders.clear();
        orders.addAll(resOrders);
    }

    /**
     * 设定的播放时间段内都需要排单
     *
     * @param fixTime
     * @param coverOrders
     * @param resOrders
     * @return
     */
    private static Long arrangeAllTime(long fixTime, List<Order> coverOrders, List<Order> resOrders) {
        Long fixTime1;
        resOrders.addAll(coverOrders);
        int orderTime = getPlayedTime(coverOrders);
        fixTime1 = fixTime - orderTime;    // 总播放时间 - 包屏时间
        return fixTime1;
    }

    /**
     * 添加满足时间的定时广告/原创广告，并打乱播放顺序
     *
     * @param resOrders
     * @param orders
     * @param fixTime1
     * @param onlyOne
     * @return
     */
    private static long shuffleOrder(List<Order> resOrders, List<Order> orders, long fixTime1, boolean onlyOne, long totalPlayTime, Double businessRate) {
        List<Integer> num = new ArrayList<>();
        for (int i = 0; i < orders.size(); i++) {
            num.add(i);
        }

        Collections.shuffle(num);
        if (onlyOne) {
            // 轮播广告添加满足时间条件的定时广告
            long businessProgramPlayTime = 0;
            for (int i = 0; i < num.size(); i++) {
                Order order = orders.get(i);
                businessProgramPlayTime += order.getPlayTime();
                if (Objects.nonNull(businessRate)) {
                    if ((double) businessProgramPlayTime / totalPlayTime > businessRate) {
                        break;
                    }
                }
                if (fixTime1 - order.getPlayTime() > 0) {
                    resOrders.add(order);
                    fixTime1 -= order.getPlayTime();
                }
            }
        } else {
            // 轮播广告+定时广告，添加满足条件的原创广告
            Random random = new Random();

            boolean firstLite = true;
            while (true) {
                int index = random.nextInt(orders.size());
                Order order = orders.get(index);
                if (order.getPlayTime() > 15 && firstLite) {
                    // TODO 存在疑问点
                    firstLite = false;
                    continue;
                }
                if (null == order || order.getPlayTime() <= 0) {
                    break;
                } else if (fixTime1 - order.getPlayTime() > 0) {
                    resOrders.add(order);
                    fixTime1 -= order.getPlayTime();
                    firstLite = true;
                } else {
                    break;
                }
            }
            orders.sort((x, y) -> Integer.compare(x.getPlayTime(), y.getPlayTime()));

            // TODO 存在疑问点
            for (int i = 0; i < orders.size() - 1; i++) {
                if (orders.get(i).getPlayTime() == fixTime1
                        || orders.get(i).getPlayTime() < fixTime1
                        && orders.get(i + 1).getPlayTime() > fixTime1) {
                    resOrders.add(orders.get(i));
                    fixTime1 -= orders.get(i).getPlayTime();
                    break;
                }
            }
        }

        return fixTime1;
    }

    /**
     * 包屏总时间
     *
     * @param orders
     * @return
     */
    public static int getPlayedTime(List<Order> orders) {
        int time = 0;
        if (CollectionUtils.isEmpty(orders)) {
            return time;
        }
        for (Order order : orders) {
            if (order.getPlayType().equals(RuleConstants.PlayType.COVER.getValue())) {
                // 包屏
                time += order.getEndTime() - order.getStartTime();
            } else {
                // 非包屏广告
                time += order.getPlayTime();
            }
        }
//        time = orders.stream().mapToInt(Order::getPlayTime).sum();
        return time;
    }

    /**
     * 处理包屏、原创广告和定时广告(打乱顺序)
     *
     * @param myOrder
     */
    private static void processFreq(MyOrder myOrder) {
        Random random = new Random();

        List<Order> orders = myOrder.getOrders();
        List<Order> freqOrders = new ArrayList<>();
        Iterator<Order> iterator = orders.iterator();

        List<Order> originOrders = new ArrayList<>();
        List<Order> coverOrders = new ArrayList<>();
        while (iterator.hasNext()) {
            Order order = iterator.next();
            if (order.getCategory() == RuleConstants.OrderType.ORIGIN.getValue()) {
                originOrders.add(order);
                iterator.remove();
                continue;
            }
            if (order.getPlayType() == RuleConstants.PlayType.COVER.getValue()) {
                coverOrders.add(order);
                iterator.remove();
                continue;
            }
            if (order.getPlayFrequency() <= 1) {
                continue;
            }
            for (int i = 0; i < order.getPlayFrequency() - 1; i++) {
                Order freqOrder = order.deepCopy();
                int index = random.nextInt(freqOrders.size()+1);
                try {
                    freqOrders.add(index, freqOrder);
                } catch (Exception e) {
                    log.error("index: {}", index);
                }
            }
        }

        // 整点/半点播放广告
        List<ConfigureParameterDTO.FixOrder> fixOrders = myOrder.getFixOrders();
        if (CollectionUtil.isNotEmpty(fixOrders)) {
            for (ConfigureParameterDTO.FixOrder fixOrder : fixOrders) {
                // 包屏广告播放时间与整点和半点广告播放时间存在冲突，不加入待排节目单中
                if (checkCoverWithFixOrderHasCovered(orders, fixOrders)) {
                    continue;
                }

                Order order = new Order();
                order.setId(fixOrder.getId());
                order.setName(fixOrder.getName());
                order.setPlayTime(fixOrder.getPlayTime());
                order.setStartTime(fixOrder.getStartTime());
                order.setEndTime(fixOrder.getStartTime() + fixOrder.getPlayTime());
                order.setCategory(RuleConstants.OrderType.FIXED.getValue());
                order.setPlayType(RuleConstants.PlayType.COVER.getValue());
                coverOrders.add(order);
            }
        }

        myOrder.getOriOrders().addAll(originOrders);
        myOrder.getCovOrders().addAll(coverOrders);

        orders.addAll(freqOrders);
        Collections.shuffle(orders);
    }

    /**
     * 包屏广告播放时间与整点和半点广告播放时间存在冲突
     * @param orders
     * @param fixOrders
     * @return
     */
    private static boolean checkCoverWithFixOrderHasCovered(List<Order> orders, List<ConfigureParameterDTO.FixOrder> fixOrders) {
        List<Order> coverOrders = orders.stream().filter(x -> x.getPlayType() == 1).collect(Collectors.toList());
        for (Order coverOrder : coverOrders) {
            for (ConfigureParameterDTO.FixOrder fixOrder : fixOrders) {
                long fixEndTime = fixOrder.getStartTime() + fixOrder.getPlayTime();
                // 左侧不重叠
                boolean leftCoverFlag = coverOrder.getStartTime() < fixOrder.getStartTime() &&
                        coverOrder.getEndTime() < fixOrder.getStartTime();
                // 右侧不重叠
                boolean rightCoverFlag = coverOrder.getStartTime() > fixEndTime  && coverOrder.getEndTime() > fixEndTime;
                if (!(leftCoverFlag || rightCoverFlag)) {
                    // 存在包屏广告和整点/半点播放广告播放时间冲突
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @description: 监听迭代
     * @params: [ga]
     * @return: void
     * @author: chenzhiwen
     * @dateTime: 2021/10/21 下午7:13
     */
    private static void addListener(GeneticAlgorithm<MyOrder, Double> ga) {
        // just for pretty print
        log.info(String.format("%s\t%s\t%s", "iter", "fit", "chromosome"));

        // Lets add listener, which prints best chromosome after each iteration
        ga.addIterationListener(new IterartionListener<MyOrder, Double>() {

            private final double threshold = 1e-5;
            private int bestCount = 0;
            private int sameCount = 0;
            private double bestF = 0;

            @Override
            public void update(GeneticAlgorithm<MyOrder, Double> ga) {

                MyOrder best = ga.getBest();
                List<MyOrder> myOrders = ga.getAll();
                //输出当前代个体适应度值
                String fi = getFit(ga, myOrders);
                double bestFit = ga.fitness(best);
                int iteration = ga.getIteration();

                // Listener prints best achieved solution
                if (iteration % 1000 == 0) {
                    log.info(String.format("%s\t%s\t%s", iteration, bestFit, fi));
                }

                // If fitness is satisfying - we can stop Genetic algorithm
                if (this.threshold > bestFit) {
                    this.bestCount++;
                    if (100 <= this.bestCount) {
                        ga.terminate();
                    }
                }
//                if(this.bestF - bestFit<1.0){
//                    this.sameCount++;
//                    if(this.sameCount>=10000){
//                        checkCover(best.orders, best.covOrders);
//                    }
//                }
//                this.bestF = bestFit;

            }
        });
    }

    private static String getFit(GeneticAlgorithm<MyOrder, Double> ga, List<MyOrder> myOrders) {
        StringBuilder desc = new StringBuilder();
        myOrders.forEach(myOrder -> {
            String x = ga.fitness(myOrder) + " ";
            desc.append(x);

        });
        return desc.toString();
    }

    /**
     * 广告重新排单：
     *  1.新广告是否轮播广告，根据起始时间判断该广告是否允许播放
     *  2.其他类型广告，判断是否能够插入order中。能，重新排单；不能，将已排好的广告单直接返回
     *
     * @param dto
     */
    @Async
    public void rearrangeProgram(RearrangeParameterDTO dto) {
        this.openTime = dto.getFixProgram().getIsArrage() ? dto.getOpenTime() + dto.getFixProgram().getPlayTime() : dto.getOpenTime();
        this.closeTime = dto.getFixProgram().getIsArrage() ? dto.getCloseTime() - dto.getFixProgram().getPlayTime() : dto.getCloseTime();

        // 轮播广告
        Order newOrder = dto.getNewOrder();
        Boolean canInsert = checkProgramInsert(dto, newOrder);

        // 种群初始化
        Population<MyOrder> population;
        if (canInsert) {
            // 能插入
            population = createInitialPopulation(5, openTime, closeTime, dto.getNewOrder(), dto.getOrders(), dto.getOnlyArrangeFirst30Minute(), dto.getBusinessRate());
        } else {
            // 不能插入，按照原算法运行
            population = createInitialPopulation(5, openTime, closeTime, dto.getOrders(), dto.getOnlyArrangeFirst30Minute(), dto.getBusinessRate(), null);
        }

        Fitness<MyOrder, Double> fitness = new MyVectorFitness();

        List<Integer> rules = initRule(dto.getNotAdjacentPrograme());

        GeneticAlgorithm<MyOrder, Double> ga = new GeneticAlgorithm<MyOrder, Double>(population, fitness, rules);

        addListener(ga);

        ga.evolve(20000);

        log.info("finished!");

        List<ProgramPlayOrderDTO> arrangeResult = sout(ga.getPopulation().getChromosomeByIndex(0).getOrders());
        if (dto.getOnlyArrangeFirst30Minute()) {
            arrangeResult = fillRestTime(arrangeResult);
        }

        boolean isJoin = false;
        if (canInsert) {
            // 查询新的广告是否在拍好的广告列表中
            for (ProgramPlayOrderDTO programPlayOrderDTO : arrangeResult) {
                if (newOrder.getName().equals(programPlayOrderDTO.getName())) {
                    isJoin = true;
                    break;
                }
            }
        }

        // 返回体
        RearrangeResultReturnDTO resultReturnDTO = new RearrangeResultReturnDTO();
        resultReturnDTO.setDate(dto.getDate());
        resultReturnDTO.setLandmarkId(dto.getLandmarkId());
        resultReturnDTO.setData(dto.getData());
        resultReturnDTO.setList(arrangeResult);
        resultReturnDTO.setIsJoin(isJoin);

        String url = "http://106.15.51.118:8087/api/eam/callback/schedule";
        remoteCall(resultReturnDTO, url);
    }

    /**
     * 预处理，判断广告是否能插入
     *
     * @param dto
     * @param newOrder
     * @return
     */
    private Boolean checkProgramInsert(RearrangeParameterDTO dto, Order newOrder) {
        Boolean canInsert = true;
        if (newOrder.getPlayType().equals(RuleConstants.PlayType.COVER.getValue())) {
            for (Order order : dto.getOrders()) {
                if (order.getPlayType().equals(RuleConstants.PlayType.COVER.getValue())) {
                    // 判断待排单广告是否在轮播广告的左侧
                    Boolean checkNewOrderArrangeLeft = newOrder.getStartTime() < order.getStartTime() && newOrder.getEndTime() <= order.getStartTime();
                    Boolean checkNewOrderArrangeRight = newOrder.getStartTime() >= order.getEndTime() && newOrder.getEndTime() > order.getEndTime();
                    // 判断待排单广告是否在轮播广告的右侧
                    if (!(checkNewOrderArrangeLeft || checkNewOrderArrangeRight)) {
                        // 不能插入
                        canInsert = false;
                        break;
                    } else {
                        // 判断是否在允许播放的时间段内
                        Integer endTime = dto.getOnlyArrangeFirst30Minute() ? this.openTime + 1800 : this.closeTime;
                        if (!(newOrder.getStartTime() >= this.openTime && newOrder.getStartTime() < endTime &&
                                newOrder.getEndTime() > this.openTime && newOrder.getEndTime() <= endTime)) {
                            // 不能插入
                            canInsert = false;
                            break;
                        }
                    }
                }
            }
        }
        return canInsert;
    }

    private Population<MyOrder> createInitialPopulation(int populationSize, Integer openTime, Integer closeTime,
                                                        Order newOrder, List<Order> orders, Boolean onlyArrangeFirst30Minute,
                                                        Double businessRate) {

        Population<MyOrder> population = new Population<MyOrder>();
        if (onlyArrangeFirst30Minute) {
            closeTime = openTime + 1800;
        }
        MyOrder base = new MyOrder(openTime, closeTime, orders);

        processFreq(base);
        processTime(base, newOrder, onlyArrangeFirst30Minute, businessRate);

        boolean coverFirst = true;
        for (int i = 0; i < populationSize; i++) {
            // each member of initial population
            // is mutated clone of base chromosome
            MyOrder chr = base.mutate();
            if (coverFirst) {
                try {
//                  checkCover(chr.orders, chr.covOrders);
                    coverFirst = false;
                    population.addChromosome(chr);
                    continue;
                } catch (Exception e) {
                    log.error("check cover order error");
                }
            }
//            Collections.shuffle(chr.orders);
            population.addChromosome(chr);
        }
        return population;
    }

    /**
     * 判断新广告能否被插入
     *
     * @param base
     * @param newOrder
     * @param onlyArrangeFirst30Minute
     */
    private void processTime(MyOrder base, Order newOrder, Boolean onlyArrangeFirst30Minute, Double businessRate) {
        List<Order> orders = base.getOrders();   // 定时广告
        Iterator<Order> iterator = orders.iterator();

        List<Order> originOrders = base.getOriOrders();
        List<Order> coverOrders = base.getCovOrders();

        long fixTime = base.getEndTime() - base.getStartTime();
        // 根据总播放时间，添加满足播放时长的定时广告和原创广告
        fillTime(fixTime, orders, coverOrders, originOrders, newOrder, onlyArrangeFirst30Minute, base.getStartTime(), businessRate);
    }

    /**
     * 填充播放时间
     *
     * @param fixTime
     * @param orders
     * @param coverOrders
     * @param originOrders
     */
    private void fillTime(long fixTime, List<Order> orders, List<Order> coverOrders, List<Order> originOrders, Order newOrder,
                          Boolean onlyArrangeFirst30Minute, Integer startTime, Double businessRate) {
        if (fixTime <= 0) {
            return; // 节目时间不正确
        }

        List<Order> resOrders = new ArrayList<>();
        Long fixTime1;
        boolean isCoverProgram = false;
        boolean isOriginalProgram = false;

        // 判断新广告类型
        if (newOrder.getPlayType().equals(RuleConstants.PlayType.COVER.getValue())) {
            isCoverProgram = true;
        } else if (newOrder.getCategory().equals(RuleConstants.OrderType.ORIGIN.getValue())) {
            isOriginalProgram = true;
        }

        if (onlyArrangeFirst30Minute) {
            // 只排前30分钟广告
            Integer endTime = startTime + 30 * 60;
            coverOrders.forEach(coverOrder -> {
                if (coverOrder.getStartTime() >= startTime && coverOrder.getEndTime() <= endTime) {
                    resOrders.add(coverOrder);
                }
            });
            int orderTime = getPlayedTime(resOrders);
            fixTime1 = fixTime - orderTime;    // 总播放时间 - 包屏时间
        } else {
            // 设定的播放时间段内都需要排单
            fixTime1 = arrangeAllTime(fixTime, coverOrders, resOrders);
        }

        if (isCoverProgram) {
            fixTime1 -= newOrder.getPlayTime();
            resOrders.add(newOrder);
        }

        if (fixTime1 > 0) { // 包屏广告时间未用完
            fixTime1 = shuffleOrder(resOrders, orders, newOrder, fixTime1, isCoverProgram, isOriginalProgram, true, fixTime, businessRate); // 包屏广告中添加定时节目
            shuffleOrder(resOrders, originOrders, newOrder, fixTime1, isCoverProgram, isOriginalProgram, false, fixTime, businessRate);
        }

        orders.clear();
        orders.addAll(resOrders);
    }

    private Long shuffleOrder(List<Order> resOrders, List<Order> orders, Order newOrder, long fixTime1,
                              boolean isCoverProgram, boolean isOriginalProgram, boolean onlyOne, long totalPlayTime, Double businessRate) {
        List<Integer> num = new ArrayList<>();
        for (int i = 0; i < orders.size(); i++) {
            num.add(i);
        }

        Collections.shuffle(num);
        if (onlyOne) {
            // 插入新的轮播广告
            long businessProgramPlayTime = 0;
            if (!isCoverProgram && !isOriginalProgram) {
                for (int i = 0; i < newOrder.getPlayFrequency(); i++) {
                    Order freqOrder = newOrder.deepCopy();
                    businessProgramPlayTime += freqOrder.getPlayTime();
                    if (Objects.nonNull(businessRate)) {
                        if ((double) businessProgramPlayTime / totalPlayTime > businessRate) {
                            break;
                        }
                    }
                    if (fixTime1 - freqOrder.getPlayTime() > 0) {
                        resOrders.add(freqOrder);
                        fixTime1 -= freqOrder.getPlayTime();
                    } else {
                        break;
                    }
                }
            }

            // 轮播广告添加满足时间条件的定时广告
            for (int i = 0; i < num.size(); i++) {
                Order order = orders.get(i);
                businessProgramPlayTime += order.getPlayTime();
                if (Objects.nonNull(businessRate)) {
                    if ((double) businessProgramPlayTime / totalPlayTime > businessRate) {
                        break;
                    }
                }
                if (fixTime1 - order.getPlayTime() > 0) {
                    resOrders.add(order);
                    fixTime1 -= order.getPlayTime();
                }
            }
        } else {
            // 轮播广告+定时广告，添加满足条件的原创广告
            Random random = new Random();

            // 插入一条轮播广告
            if (!isCoverProgram && isOriginalProgram) {
                if (fixTime1 - newOrder.getPlayTime() >= 0) {
                    resOrders.add(newOrder);
                    fixTime1 -= newOrder.getPlayTime();
                }
            }

            boolean firstLite = true;
            while (true) {
                int index = random.nextInt(orders.size());
                Order order = orders.get(index);
                if (order.getPlayTime() > 15 && firstLite) {
                    // TODO 存在疑问点
                    firstLite = false;
                    continue;
                }
                if (null == order || order.getPlayTime() <= 0) {
                    break;
                } else if (fixTime1 - order.getPlayTime() > 0) {
                    resOrders.add(order);
                    fixTime1 -= order.getPlayTime();
                    firstLite = true;
                } else {
                    break;
                }
            }
            orders.sort((x, y) -> Integer.compare(x.getPlayTime(), y.getPlayTime()));

            // TODO 存在疑问点
            for (int i = 0; i < orders.size() - 1; i++) {
                if (orders.get(i).getPlayTime() == fixTime1
                        || orders.get(i).getPlayTime() < fixTime1
                        && orders.get(i + 1).getPlayTime() > fixTime1) {
                    resOrders.add(orders.get(i));
                    fixTime1 -= orders.get(i).getPlayTime();
                    break;
                }
            }
        }

        return fixTime1;
    }


//    public static class MyOrder implements Chromosome<MyOrder>, Cloneable {
//
//        private static final Random random = new Random();
//
//        //        private List<Order> orders = initOrders(100);
////        private int startTime = OPEN_TIME;
////        private int endTime = CLOSE_TIME;
////        private int endTime = 82800;
//        private Integer startTime;
//        private Integer endTime;
//
//        //        private List<Order> orders = mockOrders(true);
////        private List<Order> orders = mockOrders(false);
//        private List<Order> orders;
//
//        private List<Order> oriOrders = new ArrayList<>();  // 原创广告
//        private List<Order> covOrders = new ArrayList<>();  // 包屏广告
//
//        public MyOrder() {
//            orders = new ArrayList<>();
//        }
//
//        public MyOrder(Integer startTime, Integer endTime, List<Order> orders) {
//            this.startTime = startTime;
//            this.endTime = endTime;
//            this.orders = orders;
//        }
//
//        /**
//         * @description: 变异
//         *
//         * @params: []
//         * @return: com.chen.webservice.Demo2.MyOrder
//         * @author: chenzhiwen
//         * @dateTime: 2021/10/21 下午7:15
//         */
//        @Override
//        public MyOrder mutate() {
//           return mut(5);
//        }
//
//        /**
//         * 随机变异
//         *
//         * @param times
//         * @return
//         */
//        private MyOrder mut(int times) {
//            MyOrder result = this.clone();
//            for (int i = 0; i < times; i++) {
//
//                int count = random.nextInt(10) + 1;
//
//                // just select random element of vector
//                // and increase or decrease it on small value
//                int index1 = random.nextInt(this.orders.size());
//                int index2 = random.nextInt(this.orders.size());
//                Order order1 = result.orders.get(index1);
//                Order order2 = result.orders.get(index2);
//                result.orders.remove(index1);
//                result.orders.add(index1, order2);
//                result.orders.remove(index2);
//                result.orders.add(index2, order1);
//                if (random.nextInt(100) <= 0) {
//                    checkCover(result.orders, result.covOrders, result.oriOrders);
//                }
//            }
//
//            return result;
//        }
//
//        public void zhidinMut(List<Order> orders) {
//            int index1 = random.nextInt(orders.size());
//            int index2 = random.nextInt(orders.size());
//            Order order1 = orders.get(index1);
//            Order order2 = orders.get(index2);
//            orders.remove(index1);
//            orders.add(index1, order2);
//            orders.remove(index2);
//            orders.add(index2, order1);
//        }
//
//        /**
//         * @description: 交叉
//         * @params: [other]
//         * @return: java.util.List<com.chen.webservice.Demo2.MyOrder>
//         * @author: chenzhiwen
//         * @dateTime: 2021/10/21 下午7:15
//         */
//        @Override
//        public List<MyOrder> crossover(MyOrder other) {
//            MyOrder thisClone = this.clone();
//            MyOrder otherClone = other.clone();
//
//            return Arrays.asList(thisClone, otherClone);
//
//        }
//
//        @Override
//        protected MyOrder clone() {
//            MyOrder clone = new MyOrder();
////            clone.orders.clear();
//            clone.orders.addAll(this.orders);
////            clone.covOrders.clear();
//            clone.covOrders.addAll(this.covOrders);
////            clone.oriOrders.clear();
//            clone.oriOrders.addAll(this.oriOrders);
//            return clone;
//        }
//
//    }

//    public static class MyVectorFitness implements Fitness<MyOrder, Double> {
//
//        @Override
//        public Double calculate(MyOrder chromosome, List<Integer> rules) {
//            double delta = 0;
//
//            int ruleRate = 0;
//
//            for (RuleHandler ruleHandler : ruleHandleChain) {
//                if (ruleHandler.ruleNum() == coverHandler.ruleNum()) {
//                    ruleRate += coverHandler.checkResultWithCover(chromosome.orders, chromosome.covOrders,
//                            chromosome.oriOrders, chromosome.startTime);
//                } else if (ruleHandler.isMatch(rules)) {
//                    ruleRate += ruleHandler.checkResult(chromosome.orders, ruleRate);
//                }
//            }
//
//            return delta + ruleRate;
//        }
//    }



   /* public static void main(String[] args) {
        Population<MyOrder> population = createInitialPopulation(5);

        Fitness<MyOrder, Double> fitness = new MyVectorFitness();

        List<Integer> rules = initRule();

        GeneticAlgorithm<MyOrder, Double> ga = new GeneticAlgorithm<MyOrder, Double>(population, fitness, rules);

        addListener(ga);

        ga.evolve(20000);

        System.out.println("finished!");

        sout(ga.getPopulation().getChromosomeByIndex(0).orders);
//
//        testFuc();
    }*/

}
